// 参考： https://threejsfundamentals.org/threejs/lessons/threejs-post-processing.html
import { 
  Mesh,
  PerspectiveCamera,
  Scene, 
  WebGLRenderer,
  TextureLoader,
  MeshPhongMaterial,
  Color,
  Vector2,
  SphereGeometry,
  Object3D,
  Vector3,
  SpotLight,
  AmbientLight,
} from "three";
import stats from '../utils/stats';
// 调用
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
import { GUI } from "dat.gui";
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass";
import { CopyShader } from "three/examples/jsm/shaders/CopyShader";
// 自定义灰度着色器
const CustomGrayScaleShader = {
  uniforms: {
    tDiffuse: { type: 't', value: null },
    rPower: { type: 'f', value: 0.2126 },
    gPower: { type: 'f', value: 0.7152 },
    bPower: { type: 'f', value: 0.0722 }
  },
  vertexShader: [
    'varying vec2 vUv;',
    'void main() {',
      'vUv = uv;',
      'gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);',
    '}'
  ].join('\n'),
  fragmentShader: [

    // 接收定义的字段
    "uniform float rPower;",
    "uniform float gPower;",
    "uniform float bPower;",

    // pass in the image/texture we'll be modifying
    "uniform sampler2D tDiffuse;",

    // used to determine the correct texel we're working on
    "varying vec2 vUv;",

    // executed, in parallel, for each pixel
    "void main() {",

    // get the pixel from the texture we're working with (called a texel)
    "vec4 texel = texture2D( tDiffuse, vUv );",

    // calculate the new color
    "float gray = texel.r*rPower + texel.g*gPower + texel.b*bPower;",

    // return this new color
    "gl_FragColor = vec4( vec3(gray), texel.w );",

    "}"

  ].join("\n")
}
// 自定义位着色器
const CustomBitShader = {

  uniforms: {
    "tDiffuse": {type: "t", value: null},
    "bitSize": {type: "i", value: 4}
  },

  vertexShader: [
    "varying vec2 vUv;",
    "void main() {",
    "vUv = uv;",
    "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
    "}"
  ].join("\n"),

  fragmentShader: [
    "uniform int bitSize;",
    "uniform sampler2D tDiffuse;",
    "varying vec2 vUv;",
    "void main() {",
      "vec4 texel = texture2D( tDiffuse, vUv );",
      "float n = pow(float(bitSize),2.0);",
      "float newR = floor(texel.r*n)/n;",
      "float newG = floor(texel.g*n)/n;",
      "float newB = floor(texel.b*n)/n;",
      "gl_FragColor = vec4( vec3(newR,newG,newB), 1.0);",
    "}"
  ].join("\n")

};

let textureLoader = new TextureLoader()
let renderer = setRenderer();
renderer.autoClear = false;
let camera = setCamera();
let scene = new Scene();
scene.background = textureLoader.load('/textures/bg/starry-deep-outer-space-galaxy.jpg')

var earthAndLight = addEarth(scene);


let renderPass = new RenderPass(scene, camera);
let customGrayScale = new ShaderPass(CustomGrayScaleShader);
let customBit = new ShaderPass(CustomBitShader);
let effectCopy = new ShaderPass(CopyShader);

let composer = new EffectComposer(renderer);
composer.addPass(renderPass);
composer.addPass(customGrayScale);
composer.addPass(customBit);
composer.addPass(effectCopy);

const trackballControls = new OrbitControls(camera, renderer.domElement);

let gui = new GUI();
addShaderControl(gui, "CustomGray", customGrayScale, { 
  floats: [
    { key: "rPower", from: 0, to: 1, step: 0.01 }, 
    { key: "gPower", from: 0, to: 1, step: 0.01 }, 
    { key: "bPower", from: 0, to: 1, step: 0.01 }
  ]});
addShaderControl(gui, "CustomBit", customBit, { 
  floats: [{ key: "bitSize", from: 1, to: 16, step: 1 }]
});

render();
function render() {
  stats.update();
  trackballControls.update();

  earthAndLight.earth.rotation.x += 0.0001;
  earthAndLight.earth.rotation.y += 0.001;

  requestAnimationFrame(render);
  composer.render();
}


function setRenderer() {
  const renderer = new WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  // 支持阴影生成 
  renderer.shadowMap.enabled = true;
  document.body.appendChild(renderer.domElement);

  return renderer
}

function setCamera(): PerspectiveCamera {
  let camera = new PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
  camera.position.set(0, 20, 40);

  return camera;
}


function addEarth(scene: Scene) {
  let textureLoader = new TextureLoader();
  let planetMaterial = new MeshPhongMaterial({
    map: textureLoader.load("/textures/earth/Earth.png"),
    normalMap: textureLoader.load("/textures/earth/EarthNormal.png"),
    specularMap: textureLoader.load("/textures/earth/EarthSpec.png"),
    specular: new Color(0x4444aa),
    normalScale: new Vector2(6,6),
    shininess: 0.5
  });
  
  let earth = new Mesh(new SphereGeometry(15, 40, 40), planetMaterial);
  scene.add(earth);
  let pivot = new Object3D();
  initDefaultLighting(pivot);
  scene.add(pivot);

  return {earth: earth, pivot: pivot};
}

function initDefaultLighting(scene: any, initialPosition?: any) {
  let position = (initialPosition !== undefined) ? initialPosition : new Vector3(-10, 30, 40);
  
  let spotLight = new SpotLight(0xffffff);
  spotLight.position.copy(position);
  spotLight.shadow.mapSize.width = 2048;
  spotLight.shadow.mapSize.height = 2048;
  spotLight.shadow.camera.fov = 15;
  spotLight.castShadow = true;
  spotLight.decay = 2;
  spotLight.penumbra = 0.05;
  spotLight.name = "spotLight"

  scene.add(spotLight);

  let ambientLight = new AmbientLight(0x343434);
  ambientLight.name = "ambientLight";
  scene.add(ambientLight);  
}

interface ITF_controls {
  [name: string]: any
}
function addShaderControl(gui: GUI, folderName: string, shaderPass: any, toSet: any, enabled?: any) {
  function uniformOrDefault(uniforms: any, key: string, def: any) {
    return (uniforms[key].value !== undefined && uniforms[key].value !== null ) ? uniforms[key].value : def;
  }

  function addUniformBool(folder: GUI, key: string, shader: any) {
    let localControls: ITF_controls = {};
    localControls[key] = uniformOrDefault(shader.uniforms, key, 0);
    folder.add(localControls, key).onChange(function(v) {shader.uniforms[key].value = v}); 
  }

  function addUniformFloat(folder: GUI, key: string, from: any, to: any, step: any, shader: any) {
    let localControls: ITF_controls = {};
    localControls[key] = uniformOrDefault(shader.uniforms, key, 0);
    folder.add(localControls, key, from, to, step).onChange(function(v) {shader.uniforms[key].value = v});
  }

  function addUniformColor(folder: GUI, key: string, shader: any) {
    let localControls: ITF_controls = {}
    localControls[key] = uniformOrDefault(shader.uniforms, key, new Color(0xffffff));
    folder.addColor( localControls, key ).onChange( function ( value ) {  
      shader.uniforms[key].value = new Color().setRGB(value.r / 255, value.g / 255, value.b / 255);
      }
    );
  }

  function addUniformVector3(folder: GUI, key: string, shader: any, from: any, to: any, step: any) {
    let startValue = uniformOrDefault(shader.uniforms, key, new Vector3(0, 0, 0));
    let keyX = key + "_x";
    let keyY = key + "_y";
    let keyZ = key + "_z";

    let localControls: ITF_controls = {};
    localControls[keyX] = startValue.x;
    localControls[keyY] = startValue.y;
    localControls[keyZ] = startValue.z;

    folder.add(localControls, keyX, from.x, to.x, step.x).onChange(function(v) {shader.uniforms[key].value.x = v});
    folder.add(localControls, keyY, from.x, to.x, step.x).onChange(function(v) {shader.uniforms[key].value.y = v});
    folder.add(localControls, keyZ, from.x, to.x, step.x).onChange(function(v) {shader.uniforms[key].value.z = v});
  }

  function addUniformVector2(folder: GUI, key: string, shader: any, from: any, to: any, step: any) {
    let startValue = uniformOrDefault(shader.uniforms, key, new Vector2(0, 0));
    shader.uniforms[key].value = startValue;

    let keyX = key + "_x";
    let keyY = key + "_y";

    let localControls: ITF_controls = {};
    localControls[keyX] = startValue.x;
    localControls[keyY] = startValue.y;

    folder.add(localControls, keyX, from.x, to.x, step.x).onChange(function(v) {shader.uniforms[key].value.x = v});
    folder.add(localControls, keyY, from.x, to.x, step.x).onChange(function(v) {shader.uniforms[key].value.y = v});
  }

  // create the folder and set enabled
  let folder = gui.addFolder(folderName);
  if (toSet.setEnabled !== undefined ? toSet.setEnabled : true) {
    shaderPass.enabled = enabled !== undefined ? enabled : false;
    folder.add(shaderPass, "enabled");
  }

  if (toSet.floats !== undefined) {
    toSet.floats.forEach(function (p: any) {
      let from = p.from !== undefined ? p.from : 0;
      let to = p.from !== undefined ? p.to : 1;
      let step = p.from !== undefined ? p.step : 0.01;
      addUniformFloat(folder, p.key, from, to, step, shaderPass)
    });
  }

  if (toSet.colors !== undefined) {
    toSet.colors.forEach(function (p: any) {
      addUniformColor(folder, p.key, shaderPass)
    });
  }

  if (toSet.vector3 !== undefined) {
    toSet.vector3.forEach(function (p: any) {
      addUniformVector3(folder, p.key, shaderPass, p.from, p.to, p.step)
    });
  }

  if (toSet.vector2 !== undefined) {
    toSet.vector2.forEach(function (p: any) {
      addUniformVector2(folder, p.key, shaderPass, p.from, p.to, p.step)
    });
  }

  if (toSet.booleans !== undefined) {
    toSet.booleans.forEach(function(p: any) {
      addUniformBool(folder, p.key, shaderPass)
    })
  }
}
  
